using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;

namespace Clandestine.Audio
{
	public enum Channels
	{
		Mono,
		Stereo
	}
	
	[AttributeUsage (AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	internal sealed class DecoderHandlesFileTypeAttribute : Attribute
	{
		private List<string> extList = new List<string>();
		
		public bool Handles(string ext)	{ return (extList.Contains(ext)); }
		
		/// <summary>
		/// Dictates the file extensions that can be loaded by this decoder.  Give more, not less!
		/// </summary>
		/// 
		/// <param name="extensions">
		/// A <see cref="System.String[]"/> representing the file extensions that can be loaded by this Decoder.
		/// For example, an ogg vorbis decoder might have new string[] { "ogg" } here.  Case-insensitive, btw.
		/// </param>
		/// 
		/// <remarks>
		/// The Decoder-finding logic first filters down the list of possible valid Decoders for a particular
		/// file by checking the file extension of the file against the entries in this array, rejecting the Decoder
		/// if the file extension is not present.
		/// 
		/// The list of remaining Decoders is then sorted so that Decoders that implement IStreamingDecoder will be
		/// tried before those that do not implement IStreamingDecoder.
		/// 
		/// Then, one at a time from the start of the list, a new instance of one of the remaining Decoders is 
		/// created, and TryStartDecode() is called with the file's filename as arguments.
		/// 
		/// If TryStartDecode() returns false, the Decoder is destroyed and the next one in the list is tried.
		/// 
		/// Once true has been returned from a Decoder, the Length property is checked.  If it is more than
		/// a few seconds, the Decoder will be used as an IStreamingDecoder, if it implements this interface.
		/// If Length is short or the Decoder does not implement IStreamingDecoder, it will be used as an
		/// ISimpleDecoder (which the abstract base class Decoder inherits from anyway, so all decoders must
		/// implement this).
		/// </remarks>
		public DecoderHandlesFileTypeAttribute(string[] extensions)
		{ 
			// Make lower-case.
			for (int i = 0; i < extensions.Length; i++)
				extensions[i] = extensions[i].ToLower();
				
			extList.AddRange(extensions);
		}
	}
	
	internal abstract class Decoder
	{
		public Decoder() { } // empty constructor, as we will be creating Decoders programatically!
		
		public abstract bool TryStartDecode(string filename);
		public abstract void EndDecode();
		
		public abstract void Rewind();
		
		public abstract Channels Channels { get; }
		public abstract TimeSpan Length { get; }
		public abstract int Rate { get; }
		public abstract bool EndOfStream { get; }
		
		public abstract byte[] DecodeChunkPCM16bit();
		
		public bool Loop = false;
		
		internal static Decoder AutoCreateDecoder(string filename)
		{
			// 0. Make sure the damn file actually exists and can be read!  (This is meant to ease debugging...)
			File.OpenRead(filename).ReadByte();
			
			// 1. Find Decoders.
			Type[] ts = typeof(Decoder).Module.FindTypes(new TypeFilter(isDecoderTypeFilter), null);

			string extension = Path.GetExtension(filename).ToLower().Substring(1);
			
			// 2. Build whitelist of Decoders that say they can handle this extension.
			List<Type> whitelist = new List<Type>();
			
			foreach (Type t in ts)
			{
				// Grab the DecoderHandlesFileTypeAttribute from the class in question.
				DecoderHandlesFileTypeAttribute dhfta
					= (DecoderHandlesFileTypeAttribute)
						t.GetCustomAttributes(typeof(DecoderHandlesFileTypeAttribute), false)[0];
				
				if (dhfta.Handles(extension))
					whitelist.Add(t);
			}
			
			if (whitelist.Count == 0)
				throw new Exception("No Decoders advertise themselves as handling file extension '" + extension
				                    + "'.");
			
			// 3. Sort List, so that Decoders implementing IStreamingDecoder have a higher priority than otherwise.
			//whitelist.Sort(new Comparison<Type>(streamBeforeNonStreamDecoderSortFunc));
			
			// 4. Create & try each Decoder until one works.  Yeah, I know.
			// It's pretty important that Decoder.TryStartDecode() returns false pretty damn quickly if the file
			// is invalid, otherwise our loading performance is probably going to go through the floor.
			Decoder d = null;
			bool success = false;
			foreach (Type t in whitelist)
			{
				// Find the public constructor with no parameters, and invoke it to make a new instance!
				d = (Decoder) t.GetConstructor(new Type[] { }).Invoke(new object[] { });
				
				// Ok, now try opening the file.
				success = d.TryStartDecode(filename);
				
				if (success)
					break;
			}
			
			if (!success)
				throw new Exception("All " + whitelist.Count.ToString() + " Decoder(s) failed to load audio file "
				                    + "'" + filename + "'.");
			
			return d;
		}
		
		private static bool isDecoderTypeFilter(Type m, object filterCriteria)
		{
			return m.IsClass && (!m.IsAbstract) && m.IsSubclassOf(typeof(Decoder));
		}
			
		/*private static int streamBeforeNonStreamDecoderSortFunc(Type x, Type y)
		{
			int x1, y1;
			x1 = (doesTypeImplementIStreamingDecoder(x) ? 0 : 1);
			y1 = (doesTypeImplementIStreamingDecoder(y) ? 0 : 1);
			return x1.CompareTo(y1);
		}
		
		private static bool doesTypeImplementIStreamingDecoder(Type t)
		{
			foreach (Type impl in t.GetInterfaces())
				if (impl == typeof(IStreamingDecoder))
					return true;
			
			return false;
		}*/
		
	}
}
